package Core.plugin

import spinal.core._
import spinal.lib._
import Core.LoongArch.CSR._
import Core._

case class CsrCpuPorts(LEN : Int = 32) extends Bundle with IMasterSlave {
    val waddr = UInt(14 bits)
    val wen   = Bool()
    val wdata = Bits(LEN bits)
    val raddr = UInt(14 bits)
    val rdata = Bits(LEN bits)
    val wvalid = Bool()
    val is_ertn = Bool()
    val is_syscall = Bool()
    val is_adef = Bool()
    val is_ine = Bool()
    val is_break = Bool()
    val is_ale = Bool()
    val int_pc = Bool()
    val pc_next = Bits(32 bits)
    val excep_pc = Bits(15 bits)
    val pc = Bits(32 bits)

    override def asMaster(): Unit = {
        in(rdata, wvalid, int_pc, pc_next)
        out(waddr, wen, wdata, raddr, is_ertn, excep_pc, pc, is_syscall, is_adef, is_ine, is_break, is_ale)
    }
}

case class CsrRegfile(LEN : Int = 32, CSRNUM : Int = 0) extends Component {
    import LoongArch._
    val cpu_ports = slave(CsrCpuPorts(LEN))

    val crmd = Reg(Bits(LEN bits)) init(B(0, 28 bits) ## B(0x8, 4 bits))
    val prmd = Reg(Bits(LEN bits)) init(0)
    val estat = Reg(Bits(LEN bits)) init(0)
    val era   = Reg(Bits(LEN bits)) init(0)
    val eentry = Reg(Bits(LEN bits)) init(0)
    val save0 = Reg(Bits(LEN bits)) init(0)
    val save1 = Reg(Bits(LEN bits)) init(0)
    val save2 = Reg(Bits(LEN bits)) init(0)
    val save3 = Reg(Bits(LEN bits)) init(0)
    val is_excep = cpu_ports.is_syscall || cpu_ports.is_ertn || cpu_ports.is_adef || cpu_ports.is_ine || cpu_ports.is_break

    cpu_ports.int_pc := False
    cpu_ports.pc_next := B(0, 32 bits)
    //cpu_ports.era_pc := era
    cpu_ports.wvalid := crmd(CSR.PLV) === B(0, 2 bits)

    when(is_excep) {
        prmd(PPLV) := crmd(PLV)
        prmd(PIE) := crmd(IE)
        crmd(PLV) := B(0, 2 bits)
        crmd(IE) := False
        cpu_ports.int_pc := True
        cpu_ports.pc_next := eentry
        era := cpu_ports.pc
    }

    when(cpu_ports.is_adef) {
        estat(ECODE) := B(0x8, 6 bits)
        estat(ESUBCODE) := B(0, 9 bits)
    }.elsewhen(cpu_ports.is_ine) {
        estat(ECODE) := B(0xD, 6 bits)
    }.elsewhen(cpu_ports.is_syscall) {
        estat(ECODE) := B(0xB, 6 bits)
    }.elsewhen(cpu_ports.is_break) {
        estat(ECODE) := B(0xC, 6 bits)
    }.elsewhen(cpu_ports.is_ale) {
        estat(ECODE) := B(0x8, 6 bits)
        estat(ESUBCODE) := B(1, 9 bits)
    }


    when(cpu_ports.is_ertn) {
        crmd(PLV) := prmd(PPLV)
        crmd(IE) := prmd(PIE)
        when (estat(ECODE) === B(0x3f, 6 bits)) {
            crmd(DA) := False
            crmd(PG) := True
        }
        cpu_ports.int_pc := True
        cpu_ports.pc_next := era
    }

    when(cpu_ports.wen && cpu_ports.wvalid) {
        switch(cpu_ports.waddr) {
            is(U(CRMD, 14 bits)) {
                crmd := B(0, 23 bits) ## cpu_ports.wdata(8 downto 0)
            }
            is(U(PRMD, 14 bits)) {
                prmd := B(0, 29 bits) ## cpu_ports.wdata(2 downto 0)
            }
            is(U(ESTAT, 14 bits)) {
                estat := B(0, 30 bits) ## cpu_ports.wdata(1 downto 0)
            }
            is(U(ERA, 14 bits)) {
                era := cpu_ports.wdata
            }
            is(U(EENTRY, 14 bits)) {
                eentry := cpu_ports.wdata(31 downto 6) ## B(0, 6 bits)
            }
            is(U(SAVE0, 14 bits)) {
                save0 := cpu_ports.wdata
            }
            is(U(SAVE1, 14 bits)) {
                save1 := cpu_ports.wdata
            }
            is(U(SAVE2, 14 bits)) {
                save2 := cpu_ports.wdata
            }
            is(U(SAVE3, 14 bits)) {
                save3 := cpu_ports.wdata
            }
        }
    }

    when(cpu_ports.wen && cpu_ports.wvalid && cpu_ports.raddr === cpu_ports.waddr) {
        cpu_ports.rdata := cpu_ports.wdata
    } .otherwise {
        switch(cpu_ports.raddr) {
            is(U(CRMD, 14 bits)) {
                cpu_ports.rdata := crmd
            }
            is(U(PRMD, 14 bits)) {
                cpu_ports.rdata := prmd
            }
            is(U(ESTAT, 14 bits)) {
                cpu_ports.rdata := estat
            }
            is(U(ERA, 14 bits)) {
                cpu_ports.rdata := era
            }
            is(U(EENTRY, 14 bits)) {
                cpu_ports.rdata := eentry
            }
            is(U(SAVE0, 14 bits)) {
                cpu_ports.rdata := save0
            }
            is(U(SAVE1, 14 bits)) {
                cpu_ports.rdata := save1
            }
            is(U(SAVE2, 14 bits)) {
                cpu_ports.rdata := save2
            }
            default {
                cpu_ports.rdata := B(0, 32 bits)
            }
            is(U(SAVE3, 14 bits)) {
                cpu_ports.rdata := save3
            }
        }
    }
}

class ExcepPlugin() extends Plugin[CPUSimple] {
    override def setup(pipeline: CPUSimple): Unit = {

    }

    override def build(pipeline: CPUSimple): Unit = {
        import pipeline._
        import LoongArch._
        import pipeline.config._
        val csr_regfile = new CsrRegfile(LEN)

        fetch plug new Area {
            import fetch._
            insert(INT_PC) := csr_regfile.cpu_ports.int_pc
            insert(CSR_PC) := csr_regfile.cpu_ports.pc_next
        }

        decode plug new Area {
            import decode._
            csr_regfile.cpu_ports.raddr := input(CSR_ADDR).asUInt
            insert(CSR_RDATA) := csr_regfile.cpu_ports.rdata
        }

        execute plug new Area {
            import execute._
            val csr_wdata = Bits(LEN bits)
            val csrwr_wdata = input(RS1)
            val csrxchg_wdata = (input(RS1) & input(RS2)) | (~input(RS2) & input(CSR_RDATA))
            val is_ertn = input(CSR_CTRL) === CsrCtrlEnum.ERTN.asBits
            val is_syscall = input(CSR_CTRL) === CsrCtrlEnum.SYSCALL.asBits
            val is_break = input(CSR_CTRL) === CsrCtrlEnum.BREAK.asBits
            val is_adef = input(CSR_CTRL) === CsrCtrlEnum.ADEF.asBits
            val is_ine = input(CSR_CTRL) === CsrCtrlEnum.INE.asBits
            val excep_pc = input(CSR_CODE)
            val csr_waddr = input(CSR_ADDR)

            csr_wdata := input(CSR_CTRL).mux(
                CsrCtrlEnum.CSRWR.asBits -> csrwr_wdata,
                CsrCtrlEnum.CSRXCHG.asBits -> csrxchg_wdata,
                default -> B(0, 32 bits)
            )

            csr_regfile.cpu_ports.pc := input(PC).asBits
            csr_regfile.cpu_ports.excep_pc := excep_pc
            csr_regfile.cpu_ports.is_ertn := is_ertn
            csr_regfile.cpu_ports.is_ine := is_ine
            csr_regfile.cpu_ports.is_adef := is_adef
            csr_regfile.cpu_ports.is_break := is_break
            csr_regfile.cpu_ports.is_syscall := is_syscall
            csr_regfile.cpu_ports.waddr := csr_waddr.asUInt
            csr_regfile.cpu_ports.wdata := csr_wdata
            csr_regfile.cpu_ports.wen := input(CSR_WEN)
        }

        memaccess plug new Area {
            import memaccess._
            csr_regfile.cpu_ports.is_ale := output(IS_ALE)
        }
    }
}
